<!--
Copyright 2023 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<html>
<head>
  <meta charset="utf-8">
</head>
<link id="syntax_style" rel="stylesheet" href="logica_syntax.css" type="text/css"/>
<script src="syntax_highlighting.js"></script>

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Logica: Playground" />
<meta name="twitter:site" content="logica.dev" />
<meta name="twitter:description" content="Interactive playground for Logica programs 🏐" />
<meta name="twitter:image" content="https://logica.dev/Logica_Logo_16_9.png" />
<link rel="icon" href="Logica_Icon.png" type="image/png">

<style>
@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap');

body {
    background-color:#f1f1f1
}
.header {
      max-width: 900px;
      margin:3px;
      /*height: auto;*/
      
      display:flex;
      flex-wrap: wrap;

      height: fit-content;
      background-color: #fefefe;
      box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
      border-radius: 3px;
      text-align: left;
      padding: 10px;
      padding-left: 30px;
      margin-left: auto;
      margin-right: auto;
      padding-top: 10px;
      padding-bottom: 10px;
}
.subheader {
      padding-left: 15px;
      float: right;
      margin-left:auto;
      font-size: 30px;
      font-family: 'Ubuntu', sans-serif;
      line-height: 1.5;
      color: #55a;
      font-weight: 900;
}
.content {
      max-width: 860px;
      margin:10px;
      height: auto;
      background-color: #fefefe;
      box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
      padding-left:40px;
      padding-right:40px;
      padding-top:1px;
      padding-bottom:40px;
      border-radius: 3px;
      margin-left: auto;
      margin-right: auto;
      font-family: 'Nunito', sans-serif;
}
.wider_content {
  max-width: 1400px;
  padding: 5px;
}
.predicate_selector, .upper_bar {
  font-family: 'Roboto Mono', monospace;
  width:auto;
}
.run_button, .logical_button {
  border: 1px solid;
  padding: 5px; 
  padding-left: 20px; 
  padding-right: 20px; 
  border-radius: 10px;
  font-family: inherit; 
  font-size: inherit;
  background-color: white; 
  border-color: white; 
  box-shadow:5px 5px 10px rgba(0, 0, 0, 0.5);
  color: black;
  transition: 0.4s;
  margin-left:auto;
  float: right;
}

/* 
.emerged {
  transform: scale(1.0);
  transform-origin: 50% 50%;
}  */

.run_button:active, .run_button_quazy_active, .logical_button:active {
  box-shadow: inset 1px 1px 4px rgba(0, 0, 0, 0.3);
  background-image: linear-gradient(-45deg, #eee, #fff);
  scale: 0.95;
  transform-origin: 50% 50%;
  transition: 0.05s;
}

.run_button:active + #output {
  color: red;
}

.code_title {
  font-family: 'Roboto Mono', monospace;
  font-size:1.3em;
  margin-top: 10px;
}

@keyframes bumpup-anime {
  0% {transform: rotateZ(0deg);}
  10% {transform: rotateZ(0deg) scale(1.2); box-shadow: 5px 20px 40px rgba(0, 0, 0, 0.5);}
  80% {transform: rotateZ(0deg) scale(1.2); box-shadow: 5px 20px 40px rgba(0, 0, 0, 0.5);}
  100% {transform: rotateZ(0deg);}
}

@keyframes flip-anime {
  0% {transform: rotateZ(0deg);}
  10% {transform: rotateZ(10deg) scale(1.2); box-shadow: 5px 20px 40px rgba(0, 0, 0, 0.5);}
  40% {transform: rotateZ(10deg) scale(1.2); box-shadow: 5px 20px 40px rgba(0, 0, 0, 0.5);}
  50% {transform: rotateZ(-7deg) scale(1.3); box-shadow: 5px 20px 70px rgba(0, 0, 0, 0.5);}
  80% {transform: rotateZ(-7deg) scale(1.3); box-shadow: 5px 20px 70px rgba(0, 0, 0, 0.5);}
  100% {transform: rotateZ(0deg);}
}

.animated_bump {
  animation: bumpup-anime 1s;
}

.animated_flip {
  animation: flip-anime 0.7s;
  animation-timing-function: linear;
}

.code {
  outline-color: #222;
  border: solid 3px black;
  transition: 0.5s;
  margin-top: 10px;
  margin-bottom: 5px;
}
.plain_code {
  margin-top: 10px;
  outline-color: #222;
  border: solid 3px black;
}
.code:focus {
  /* outline-color: #222;*/
  /* outline: none;*/
  border: solid 3px greenyellow;
  overflow-y: scroll;
}

.upper_bar {
  margin-top: 5px;
}

.button_pit {
  /*box-shadow: inset 0px 0px 6px rgba(0, 0, 0, 0.6);*/
  border-radius: 10px;
  float:right;
  margin-left:auto
}

.two_screens_height {
  height: 400px; /* 370px; */
}

.two_columns_content {
  display: flex;
  flex-direction: row;
  width:100%;
}

.narrower_code {
  width: 50%;
  padding-right: 5px;
}

.upper_bar_adjusted {
  height: 30px;
}

.default_code_height {
  height: 370px;
}
</style>
<!-- # Uncomment to run predicates in sync.
  
  <script defer src="https://pyscript.net/latest/pyscript.js"></script>
-->

<body onkeydown="lastKeyPressedTime=performance.now()">

<div class="header" id="header">
    <a href="index.html">
      <img src="logica_logo.png" width="200px"/>
    </a>
    
    
    <div class="subheader" ondblclick="hideHeader()"> <!-- style="width:auto"> -->
        <!-- Logic Programming Language  <br/><span style="width: 20px;  display: inline-block;"></span>for Data Analysis-->
        <!--Logic Programming Language for Data Analysis -->
    
        <!-- Modern Logic Programming  🏖️ -->
        
        Playground(🏐)
    </div>
    <script>
      function hideHeader() {
        document.getElementById('header')['style']['display'] = 'none';
      }
    </script>

</div>



<div id="content" class="content"> <!--  wider_content -->

<div id="screens" class="screens"> <!-- two_columns_content -->

<div id="code_screen" class="code_screen"> <!-- narrower_code -->
<div class="upper_bar">
  <div class="code_title" style="display:inline">
    Program:
  </div>
  <div class="button_pit">
  <button class="logical_button" onclick="copyProgram()">
    🔗 Copy Link
  </button>
  </div>
  <script>
    function copyProgram() {
      let url = new URL(window.location.href);
      url.searchParams.set('program', document.getElementById('program').innerText);
      url.searchParams.set('main_predicate', document.getElementById('main_predicate').value);
      navigator.clipboard.writeText(url.toString());
      flipIt();
      setTimeout(() => {window.history.replaceState(null, null, url.toString())}, 700);
    }
  </script>
</div>

<div style="box-shadow: inset 0px 0px 6px rgba(0, 0, 0, 0.6);border-radius: 7px;"> 
   <!-- two_screens_height -->
  <pre class="code default_code_height" id="program" contenteditable="true" style="padding:10px;">
# Welcome to Logica Playground!
# Go ahead and edit this program. Start with editing book names and prices.
# You can write new predicates, specify which predicate you want to
# run in the input box below.
Book("From Caves to Stars", 120);
Book("Morning of Juliet", 40);
Book("Dawn of the Apes", 45);
Book("Tragedy and Discord", 124);
Book("How to Get Rich Writing Books for $5", 5);
Book("I Wrote a Book for $5, Guess What Happened Next!", 4);
Book("Breakfast at Paris", 30);
Book("My Friend: Dragon", 102);

ExpensiveBook(book_name) :-
  Book(book_name, price),
  price > 100;
  </pre>
</div>

</div>

<div id="output_screen" class="output_screen"> <!-- narrower_code -->

<div class="upper_bar">
<div id="predicate_selector" class="predicate_selector">
  <div style="display: inline;">Predicate to run:</div>
  <input id="main_predicate" style="font-family: inherit; font-size: inherit; padding: 5px; border-radius: 10px; border: solid 1px gray; " value="ExpensiveBook"></input>

  <div class="button_pit">
  <button id="the_run_button" class="run_button" onclick="executeAndOutput()">RUN (Ctrl-Enter)</button>
  </div>
</div>
<div class="code_title" style="display: none">
  Output:
</div>
</div>
<div class="plain_code" id="output"> <!-- two_screens_height -->
  Loading Logical engine...</div>
</div>
</div>

</div>
  <script>
    var worker = new Worker('logica.js');

    // Prepare to handle responses from the engine.
    worker.onmessage = function(event) {
      console.log('Logical worker responded:', event);
      worker_response = event.data;
      let hide_error = worker_response.get('hide_error');
      let status = worker_response.get('status');
      let program = worker_response.get('program');
      let predicate = worker_response.get('predicate');
      lastProgramExecuted = program;
      lastPredicateExecuted = predicate;
      if (!hide_error || status == 'OK') {
        outputResult(worker_response);
      }
    };

    // Issue an execution request.
    function requestExecution(gentle_execution) {
      let program = document.getElementById('program').innerText;
      let predicate = document.getElementById('main_predicate').value;
      if (gentle_execution && lastProgramExecuted == program && lastPredicateExecuted == predicate) {
        return;
      }
      worker.postMessage({
        type: 'run_predicate', 
        predicate: predicate, 
        program: program,
        hide_error: gentle_execution
      });
    }
  </script>
  <script>
    setInterval(() => {requestExecution(true)}, 300);
    //setInterval(executeHideError, 1000);
    var syntaxMode = 'off';
    document.addEventListener('keydown', function(event) {
      if (event.ctrlKey && event.key === 'Enter') {
        console.log('Ctrl+Enter was pressed');
        let button = document.getElementById('the_run_button');
        button.classList.add('run_button_quazy_active');
        let main_predicate = document.getElementById('main_predicate');
        let selection = window.getSelection().toString();
        if (selection != '') {
          main_predicate.value = selection;
        }
        executeAndOutput();
        setTimeout(() => {button.classList.remove('run_button_quazy_active');}, 1000);
      }
      if (event.ctrlKey && event.key == 'Escape') {
        console.log('Ctrl+Esc was pressed.');
        if (syntaxMode == 'off') {
          HighlightCodeElements(document);
          syntaxMode = 'on';
        } else {
          syntaxMode ='off';
          document.getElementById('program').innerHTML = document.getElementById('program').innerText;
        }
      }
      if (event.shiftKey && event.key == 'Escape') {
        document.getElementById('content').classList.add('wider_content');
        document.getElementById('screens').classList.add('two_columns_content');
        document.getElementById('code_screen').classList.add('narrower_code');
        document.getElementById('output_screen').classList.add('narrower_code');
        document.querySelectorAll('.upper_bar').forEach((b) => {
          b.classList.add('upper_bar_adjusted');
        });
        document.getElementById('program').classList.remove('default_code_height');

        document.getElementById('program').classList.add('two_screens_height');
        document.getElementById('output').classList.add('two_screens_height');

        document.getElementById('predicate_selector').style['display'] = 'none';
      }
      console.log(event.shiftKey, event.key);
      if (event.shiftKey && event.key == 'Enter') {
        copyProgram();
      }
    });
    
    function GetProgramFromURL() {
      let program_from_url = new URLSearchParams(window.location.search).get('program');
      let main_predicate_from_url = new URLSearchParams(window.location.search).get('main_predicate');

      if (program_from_url.length > 0) {
        document.getElementById('program').innerHTML = program_from_url;
      }
      if (main_predicate_from_url.length > 0) {
        document.getElementById('main_predicate').value = main_predicate_from_url;
      }
    }
    GetProgramFromURL();

    var lastKeyPressedTime = 0;
    function outputResult(execution_result) {
      if (execution_result.get('status') == 'OK') {
          output.innerHTML = execution_result.get('result');
        } else {
          output.innerHTML = `
<u>Rule:</u><br/>
${execution_result.get('error_context')}


[ <div style="color:red;font-weight:bold;display:inline">Error</div> ] ${execution_result.get('error_message')}
`;
          output.innerHTML = output.innerHTML.replaceAll('{logica error}-*', '<div style="color:yellow;font-weight:bold;display:inline">');
          output.innerHTML = output.innerHTML.replaceAll('*-{logica error}', '</div>');
    
        }
    }

    function bumpItUp() {
      let output = document.getElementById('output');
      output.classList.add('animated_bump');
      setTimeout(() => {output.classList.remove('animated_bump');}, 1000);
    }
    function flipIt() {
      let p = document.getElementById('program');
      p.classList.add('animated_flip');
      setTimeout(() => {p.classList.remove('animated_flip');}, 1000);
    }

    var last_queried = 0;
    function executeAndOutput() {
      let now = performance.now();
      if (now - last_queried < 500) {
        return;
      }
      last_queried = now;
      bumpItUp();

      // Old:
      // let execution_result = execute();
      // let output = document.getElementById('output');
      // outputResult(execution_result);
      requestExecution(false);
    }
    var lastProgramExecuted = "";
    var lastPredicateExecuted = "";
    function executeHideError() {
      console.log('start');
      let program = document.getElementById('program').innerText + '->' + document.getElementById('main_predicate').value;
      if (lastProgramExecuted == program) {
        console.log('end');
        return;
      }
  
      
      let execution_result = execute();
      if (execution_result.get('status') != 'OK') {
        return;
      }
      console.log('end');
      let output = document.getElementById('output');
      outputResult(execution_result);      
      lastProgramExecuted = program;

      // UpdateProgramInUrl();
    }
    function UpdateProgramInUrl() {
      let url = new URL(window.location.href);
      url.searchParams.set('program', document.getElementById('program').innerText);
      window.history.replaceState(null, null, url.toString());
    }
    function execute() {
        let program_div = document.getElementById('program');
        c = pyscript.interpreter.globals.get('RunPredicate')
        let clean_program = document.getElementById('program').innerText;
        let main_predicate = document.getElementById('main_predicate').value;
        let execution_result = c('@Engine("sqlite");' + clean_program, main_predicate);
        return execution_result;
    }
  </script>
  <py-config style="display: none">
    packages = ["logica", "sqlite3"]
  </py-config>
  <py-script style="display: none">
from logica.parser_py import parse
from logica.compiler import universe
from logica.compiler import rule_translate
from logica.common import logica_lib
from logica.common import color

color.CHR_WARNING = '{logica error}-*'
color.CHR_END = '*-{logica error}'

import csv

def CreateBooksCsvFile():
  with open('books.csv', 'w') as w:
    writer = csv.writer(w)
    # It's easier to parse without header and it is
    # impossible to use the header anyway.
    # writer.writerow(['name', 'author', 'price'])
    writer.writerow(['From Caves to Stars', 'Beans A.A.', 120])
    writer.writerow(['Morning of Juliet', 'Smitey E.', 40])
    writer.writerow(['Dawn of the Apes', 'Mon K.', 45])
    writer.writerow(['Tragedy and Discord', 'Tarklor D.', 124])
    writer.writerow(['How to Get Rich Writing Book for $5', 'Getri C. H.', 5])
    writer.writerow(['I Wrote a Book for $5 - Guess What Happened Next!', 'Getri C. H.', 4])
    writer.writerow(['Breakfast at Paris', 'Degaul C.', 30])
    writer.writerow(['My Friend: Dragon', 'Tame R.', 102])

CreateBooksCsvFile()


def RunPredicate(program, predicate):
  try:
    rules = parse.ParseFile(program)['rule']
  except parse.ParsingException as e:
    before, error, after = e.location.Pieces()
    error_context = before + "{logica error}-*" + error + "*-{logica error}" + after;
    return {"result": "", "error_context": error_context, "error_message": str(e), "status": "error", "predicate_name": predicate}
  try:
    u = universe.LogicaProgram(rules)
    sql = u.FormattedPredicateSql(predicate)
  except rule_translate.RuleCompileException as e:
    return {"result": "", "error_context": e.rule_str, "error_message": str(e), "status": "error", "predicate_name": predicate}

  try:
    data = logica_lib.RunQuery(sql, engine='sqlite')
  except Exception as e:
    return {"result": "", "error_context": sql, "error_message": "Error while executing SQL:\n%s" % e, "status": "error", "predicate_name": predicate}


  return {"result": data, "error_message": "",  "error_context": "", "status": "OK", "predicate_name": predicate}
  </py-script>
</body>
</html>